/*
 * Galaxium Messenger
 * Copyright (C) 2007 Ben Motmans <ben.motmans@gmail.com>
 * Copyright (C) 2003 Philippe Durand <draekz@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Collections.Generic;

using Gdk;
using Gtk;
using Glade;

using Galaxium.Client;
using Galaxium.Core;
using Galaxium.Gui;
using Galaxium.Gui.GtkGui;
using Galaxium.Protocol;
using Galaxium.Protocol.Gui;

using Anculus.Core;
using Anculus.Gui;

using Mono.Addins;

namespace Galaxium.Client.GtkGui
{
	public class MainWindow : IControllerWindow<Widget>
	{
		public event EventHandler VisibleChanged;
		
		[Widget ("MainWindow")]
		private Gtk.Window _main_window;
		[Widget ("menubar")]
		private MenuBar _menu_bar;
		[Widget ("toolbar")]
		private Toolbar _toolbar;
		[Widget ("vbox")]
		private VBox _vbox;
		[Widget ("image")]
		private Gtk.Image _image;
		[Widget ("imageSeparator")]
		private Gtk.HSeparator _image_separator;
		[Widget ("accountBox")]
		private VBox _account_box;
		[Widget ("hboxSessions")]
		private HBox _sessions_hbox;
		[Widget ("lblType")]
		private Label _type_label;
		[Widget ("btnConnect")]
		private Button _connect_button;
		[Widget ("btnDisconnect")]
		private Button _disconnect_button;
		[Widget ("notebook")]
		private Notebook _notebook;
		[Widget ("displayImgBox")]
		private HBox _display_image_box;
		
		const string _menu_path = "/Galaxium/Gui/MainWindow/Menu";
		
		private ImageView _display_image;
		private ImageComboBox<ISession> _sessions_combo;
		private ImageComboBox<IProtocol> _protocols_combo;
		
		private IAccountWidget<Widget> _current_account_widget;
		
		private Dictionary<ISession, int> _page_lookup = new Dictionary<ISession, int> ();
		private Dictionary<ISession, ISessionWidget<Widget>> _widget_lookup = new Dictionary<ISession, ISessionWidget<Widget>> ();
		
		private int _last_page = 0;
		IConfigurationSection _config = Configuration.ContactList.Section;
		int _window_x = 0, _window_y = 0, _window_w = 0, _window_h = 0;
		private int _shake_count = 20;
		private int _shake_step = 0;
		private int _shake_x, _shake_y;
		private int[,] _shake_coords = new int [4,2] {{ -4, -4 }, { +4, +4 }, { +4, -4 }, { -4, +4 }};
		
		private int _auto_connecting = 0;
		
		private System.Timers.Timer _shake_timer = new System.Timers.Timer (25);
		
		public bool Visible
		{
			get { return(_main_window.Visible); }
			set
			{
				if (value == _main_window.Visible)
					return;
				
				if (!value)
				{
					_main_window.GetPosition(out _window_x, out _window_y);
					_main_window.GetSize(out _window_w, out _window_h);
				}
				else
				{
					_main_window.Move(_window_x, _window_y);
					_main_window.Resize (_window_w, _window_h);
				}
				
				_main_window.Visible = value;
				
				if (VisibleChanged != null)
					VisibleChanged(this, EventArgs.Empty);
			}
		}
		
		public Widget Menubar
		{
			get { return _menu_bar; }
		}
		
		public Widget Toolbar
		{
			get { return _toolbar; }
		}
		
		public MainWindow ()
		{
			_page_lookup = new Dictionary<ISession, int> ();
			_widget_lookup = new Dictionary<ISession, ISessionWidget<Widget>> ();
			
			XML.CustomHandler = new XMLCustomWidgetHandler (CreateCustomWidget);
			XML gxml = new XML (GladeUtility.GetGladeResourceStream (typeof (MainWindow).Assembly, "MainWindow.glade"), null, null);
			gxml.Autoconnect (this);
			
			_main_window.Icon = IconUtility.GetIcon ("galaxium-contactlist", IconSizes.Large);
			_main_window.AddAccelGroup (MenuUtility.GetAccelGroup (_main_window));
			
			_connect_button.Image = new Gtk.Image (IconUtility.GetIcon ("galaxium-start", IconSizes.Small));
			_disconnect_button.Image = new Gtk.Image (IconUtility.GetIcon ("galaxium-stop", IconSizes.Small));
			_image.Pixbuf = IconUtility.GetIcon ("galaxium-banner", IconSizes.Other);
			
			_protocols_combo.Changed += new EventHandler (SelectedProtocolChanged);
			
			foreach (IProtocol protocol in ProtocolUtility.Protocols)
				_protocols_combo.Append (protocol);
			
			string last_protocol_name = Configuration.Protocol.Section.GetString (Configuration.Protocol.LastUsed.Name, Configuration.Protocol.LastUsed.Default);
			IProtocol lastProtocol = string.IsNullOrEmpty (last_protocol_name) ? null : ProtocolUtility.GetProtocol (last_protocol_name);
			
			if (lastProtocol != null)
				_protocols_combo.Select (lastProtocol);
			else
				_protocols_combo.SelectFirst ();
			
			_window_x = _config.GetInt ("X", 0);
			_window_y = _config.GetInt ("Y", 0);
			_window_w = _config.GetInt ("W", 275);
			_window_h = _config.GetInt ("H", 400);
			
			if (_config.GetBool (Configuration.ContactList.ShowOnStartup.Name, Configuration.ContactList.ShowOnStartup.Default))
			{
				_config.SetBool (Configuration.ContactList.ShowOnStartup.Name, true);
				Visible = true;
			}
			
			UpdateMenu ();
			
			_toolbar.Hide ();
			
			AddinManager.ExtensionChanged += OnExtensionChanged;
			
			SetSwitcherStatus (false);
			ActivityUtility.Ready = true;
			
			// Now that we have finished setting up the window, lets try and
			// connect the list of connections we should connect rightaway.
			
			foreach (IAccount account in AccountUtility.Accounts)
			{
				if(account.AutoConnect)
				{
					// When we are auto-connecting accounts, we shouldn't let the user connect more just yet.
					_auto_connecting++;
					
					if (_current_account_widget != null)
						_current_account_widget.DisableFields (false);
					
					IAccountWidget<Widget> account_widget = ProtocolGuiUtility.CreateAccountWidget<Widget> (account.Protocol);
					account_widget.Initialize ();
					
					account_widget.CreateSessionWidget += AccountWidgetCreateSessionWidget;
					account_widget.CloseSessionWidget += AccountWidgetCloseSessionWidget;
					account_widget.ShowLastSessionWidget += AccountWidgetShowLastSessionWidget;
					account_widget.ShowSessionWidget += AccountWidgetShowSessionWidget;
					account_widget.EnableChange += AccountEnableChange;
					account_widget.DisableChange += AccountDisableChange;
					account_widget.AccountChanged += AccountWidgetAccountChanged;
					
					account_widget.SelectAccount (account.UniqueIdentifier);
					
					account_widget.Connect ();
				}
			}
		}
		
		public static void ExecuteEntryPoint (params object[] args)
		{
			Application.Init ();
			
			//Dispatch.Initialize (new GtkGuiDispatcher ());
			ThreadUtility.Dispatcher = new GtkThreadDispatcher ();
			
			GtkUtility.Initialize ();
			ClientUtility.Initialize();
			EmoticonUtility.Initialize();
			SoundSetUtility.Initialize();
			
			GalaxiumUtility.Initialize (new MainWindow ());
			
			WindowUtility<Widget>.Initialize(new GtkWindowUtility());
			
			GtkActivityUtility.Initialize();
			
			GLib.ExceptionManager.UnhandledException += delegate (GLib.UnhandledExceptionArgs ea) {
				Exception ex = ea.ExceptionObject as Exception;
				
				Log.Fatal (ex, "Uncaught GLib exception.");
			};
			
			Application.Run ();
		}
		
		internal void Shutdown ()
		{
			SaveWindowLocation ();
			GalaxiumUtility.TransferWindow.SaveWindowLocation();
			
			WindowUtility<Widget>.CloseAll ();
			
			EmoticonUtility.Shutdown();
			SoundSetUtility.Shutdown();
			ClientUtility.Shutdown ();
			AddinUtility.Shutdown ();
			
			_main_window.Destroy ();
			GalaxiumUtility.TransferWindow.Destroy ();
			
			Application.Quit ();
		}
		
		public void ShowAccountWidget ()
		{
			SwitchView (ControllerViews.Account);
		}
		
		public void CloseActiveSession ()
		{
			if (SessionUtility.ActiveSession != null)
			{
				WindowUtility<Widget>.RemoveWindows (SessionUtility.ActiveSession);
				
				SessionUtility.ActiveSession.Disconnect ();
			}
		}
		
		public void CloseAllSessions ()
		{
			List<ISession> sessions = new List<ISession> (SessionUtility.Sessions);
			
			foreach (ISession session in sessions)
				session.Disconnect ();
		}
		
		public void UpdateAll ()
		{
			UpdateMenu ();
			
			foreach (ISession session in SessionUtility.Sessions)
			{
				if (_widget_lookup.ContainsKey (session))
					_widget_lookup[session].Update ();
			}
		}
		
		internal ISessionWidget<Widget> GetSessionWidget (ISession session)
		{
			int index;
			
			if (_page_lookup.TryGetValue (session, out index))
				return _notebook.GetNthPage (index) as ISessionWidget<Widget>;
			
			return null;
		}
		
		private Widget CreateCustomWidget (XML gxml, string func_name, string name, string string1, string string2, int int1, int int2)
		{
			if (name == "cboSessionPlaceHolder")
			{
				_sessions_combo = new ImageComboBox<ISession> (new ImageComboTextLookup<ISession> (SessionTextLookup), new ImageComboPixbufLookup<ISession> (SessionImageLookup));
				_sessions_combo.Changed += new EventHandler (SessionsChanged);
				_sessions_combo.Show();
				return _sessions_combo;
			}
			else if (name == "cboProtocolPlaceHolder")
			{
				_protocols_combo = new ImageComboBox<IProtocol> (new ImageComboTextLookup<IProtocol> (ProtocolTextLookup), new ImageComboPixbufLookup<IProtocol> (ProtocolImageLookup));
				_protocols_combo.Show();
				return _protocols_combo;
			}
			else if (name == "displayImgPlaceHolder")
			{
				_display_image = new ImageView ();
				_display_image.SetSizeRequest (96, 96);
				_display_image.ShowAll ();
				return _display_image;
			}
			
			return new Label ("ERROR");
		}
		
		public void GenerateAlert ()
		{
			if (!_main_window.HasFocus)
				_main_window.UrgencyHint = true;
		}
		
		private void SaveWindowLocation ()
		{
			if (_main_window.Visible)
			{
				int x, y, w, h;
				_main_window.GetSize (out w, out h);
				_main_window.GetPosition (out x, out y);
				
				_config.SetInt ("X", x);
				_config.SetInt ("Y", y);
				_config.SetInt ("W", w);
				_config.SetInt ("H", h);
			}
		}
		
		public void Shake ()
		{
			_main_window.GetPosition(out _shake_x, out _shake_y);
			
			_shake_count = 20;
			
			lock (_shake_timer)
			{
				if (!_shake_timer.Enabled)
					_shake_timer.Start ();
			}
		}
		
		private void ShakeTimerElapsed (object sender, System.Timers.ElapsedEventArgs args)
		{
			Random rnd = new Random();
			int change_x = _shake_coords[_shake_step, 0];
			int change_y = _shake_coords[_shake_step, 1];
			
				_main_window.Move(_shake_x + change_x, _shake_y + change_y);
			
			_shake_step++;
			_shake_count--;
			
			if (_shake_step >= 3)
				_shake_step = 0;
			
			if (_shake_count < 1)
			{
					_main_window.Move(_shake_x, _shake_y);
				
				lock (_shake_timer)
					_shake_timer.Stop();
				
				_shake_count = 20;
			}
		}
		
		public void UpdateMenu ()
		{
			MenuUtility.FillMenuBar (_menu_path, new DefaultExtensionContext (_main_window), _menu_bar);
		}
		
		public enum ControllerViews { Account, Session };
		
		private void SwitchView (ControllerViews view)
		{
			// This assumes that the SessionUtility.ActiveSession is already
			// set to the proper session we would want to view.
			
			switch (view)
			{
				case ControllerViews.Account:
					if (_current_account_widget != null)
						_current_account_widget.Clear ();
					
					_notebook.Page = 0;
					
					SetSwitcherStatus (false);
					
					UpdateMenu ();
					
					SwitchDisplayImage (_current_account_widget.Account);
					break;
				
				case ControllerViews.Session:
					if (SessionUtility.ActiveSession == null)
					{
						SwitchView (ControllerViews.Account);
						return;
					}
					
					if (_page_lookup.ContainsKey (SessionUtility.ActiveSession))
					{
						_notebook.Page = _page_lookup[SessionUtility.ActiveSession];
						_sessions_combo.Select(SessionUtility.ActiveSession);
						
						SetSwitcherStatus (true);
						
						UpdateMenu ();
						
						// This should add to the main menu if needed.
						_widget_lookup[SessionUtility.ActiveSession].SwitchTo();
					}
					else
					{
						SwitchView (ControllerViews.Account);
						return;
					}
					break;
			}
		}
		
		public void SetSwitcherStatus (bool enabled)
		{
			_sessions_hbox.Visible = enabled;
			SessionUtility.AddingSession = !enabled;
		}
		
		private void SwitchDisplayImage (IAccount account)
		{
			byte[] data = null;
			Pixbuf pbuf = null;
			
			if ((account != null) && (account.DisplayImage != null))
				data = account.DisplayImage.ImageBuffer;
			
			if ((data != null) && (data.Length > 0))
			{
				try
				{
					pbuf = new Pixbuf (data);
				}
				catch
				{
				}
			}
			
			if (pbuf == null)
				pbuf = IconUtility.GetIcon ("galaxium-displayimage");
			
			_display_image.FadeTo (PixbufUtility.GetScaledPixbuf (pbuf, 96, 96));
			_display_image_box.ShowAll ();
		}
		
		private string SessionTextLookup (ISession item)
		{
			return item.Account.UniqueIdentifier;
		}
		
		private Gdk.Pixbuf SessionImageLookup (ISession item)
		{
			return IconUtility.GetIcon (item.Account.Protocol.Image, IconSizes.Small);
		}
		
		private string ProtocolTextLookup (IProtocol item)
		{
			return item.Description;
		}
		
		private Gdk.Pixbuf ProtocolImageLookup (IProtocol item)
		{
			return IconUtility.GetIcon (item.Image, IconSizes.Small);
		}
		
		private void RemoveSession (ISession session)
		{
			int index = 0;
			
			if (_page_lookup.TryGetValue (session, out index))
			{
				_page_lookup.Remove (session);
				
				lock (_page_lookup)
				{
					foreach (KeyValuePair<ISession, int> pair in _page_lookup)
					{
						if (pair.Value > index)
							_page_lookup[pair.Key] = pair.Value - 1;
					}
				}
				
				_notebook.RemovePage (index);
				
			}
			
			_sessions_combo.Remove (session);
			SessionUtility.RemoveSession (session);
			
			session.Dispose ();
		}
		
		void OnExtensionChanged (object o, ExtensionEventArgs args)
		{
			if (args.PathChanged(_menu_path))
				UpdateMenu ();
		}
		
		private void OnMainWindowDelete (object sender, DeleteEventArgs args)
		{
			args.RetVal = true;
			GalaxiumUtility.MainWindow.Visible = !GalaxiumUtility.MainWindow.Visible;
		}
		
		private void OnConnectClicked (object sender, EventArgs args)
		{
			ShowAccountWidget ();
		}
		
		private void OnDisconnectClicked (object sender, EventArgs args)
		{
			CloseActiveSession ();
		}
		
		private void SessionsChanged (object sender, EventArgs e)
		{
			SessionUtility.ActiveSession = _sessions_combo.GetSelectedItem ();
			
			SwitchView (ControllerViews.Session);
		}
		
		private void SelectedProtocolChanged (object sender, EventArgs e)
		{
			if (_current_account_widget != null)
			{
				_account_box.Remove (_current_account_widget.NativeWidget);
				_current_account_widget = null;
			}
			
			IProtocol protocol = _protocols_combo.GetSelectedItem ();
			
			if (protocol != null)
			{
				_current_account_widget = ProtocolGuiUtility.CreateAccountWidget<Widget> (protocol);
				_current_account_widget.Initialize ();
				
				_current_account_widget.CreateSessionWidget += AccountWidgetCreateSessionWidget;
				_current_account_widget.CloseSessionWidget += AccountWidgetCloseSessionWidget;
				_current_account_widget.ShowLastSessionWidget += AccountWidgetShowLastSessionWidget;
				_current_account_widget.ShowSessionWidget += AccountWidgetShowSessionWidget;
				_current_account_widget.EnableChange += AccountEnableChange;
				_current_account_widget.DisableChange += AccountDisableChange;
				_current_account_widget.AccountChanged += AccountWidgetAccountChanged;
				
				SwitchDisplayImage (_current_account_widget.Account);
				
				_account_box.PackStart (_current_account_widget.NativeWidget, true, true, 0);
				_current_account_widget.ApplyParentLayout (_type_label);
				
				Configuration.Protocol.Section.SetString (Configuration.Protocol.LastUsed.Name, protocol.Name);
			}
		}
		
		private void AccountWidgetCloseSessionWidget (object sender, SessionEventArgs args)
		{
			_auto_connecting--;
			
			if (_auto_connecting >= 0)
				if (_current_account_widget != null)
					_current_account_widget.EnableFields ();
			
			RemoveSession (args.Session);
			
			SwitchView (ControllerViews.Session);
		}
		
		private void AccountWidgetShowLastSessionWidget (object sender, EventArgs args)
		{
			SwitchView (ControllerViews.Session);
		}
		
		private void AccountWidgetShowSessionWidget (object sender, SessionEventArgs args)
		{
			SessionUtility.ActiveSession = args.Session;
			
			SwitchView (ControllerViews.Session);
		}
		
		private void SessionWidgetActivateChatWidget (object sender, ChatEventArgs args)
		{
			WindowUtility<Widget>.Activate(args.Conversation, true);
		}
		
		private void AccountWidgetCreateSessionWidget (object sender, SessionEventArgs args)
		{
			IAccountWidget<Widget> aw = sender as IAccountWidget<Widget>;
			ISessionWidget<Widget> widget = ProtocolGuiUtility.CreateSessionWidget<Widget> (aw.Protocol, this, args.Session);
			widget.ActivateChatWidget += SessionWidgetActivateChatWidget;
			widget.Initialize ();
			
			_notebook.AppendPage (widget.NativeWidget, new Label (String.Empty));
			
			int page = _notebook.NPages - 1;
			_widget_lookup.Add(args.Session, widget);
			_page_lookup.Add (args.Session, page);
			_sessions_combo.Append (args.Session, false);
			
			SessionUtility.AddSession (args.Session);
			SessionUtility.ActiveSession = args.Session;
			
			SwitchView (ControllerViews.Session);
			
			_last_page = page;
			
			// Now that we have the GUI aspect taken care of, we need to listen for certain
			// events that we will be creating activities out of.
			
			args.Session.TransferInvitationSent += SessionTransferInvitationSent;
			
			ActivityUtility.EmitActivity (this, new EntityPresenceChangeActivity (args.Session.Account, args.Session.Account.Presence));
		}
		
		public void SessionTransferInvitationSent (object sender, FileTransferEventArgs args)
		{
			FileTransferUtility.Add (args.FileTransfer);
			
			GalaxiumUtility.TransferWindow.Show ();
		}
		
		private void AccountEnableChange (object sender, EventArgs args)
		{
			_protocols_combo.Sensitive = true;
		}
		
		private void AccountDisableChange (object sender, EventArgs args)
		{
			_protocols_combo.Sensitive = false;
		}
		
		void AccountWidgetAccountChanged (object sender, AccountEventArgs args)
		{
			SwitchDisplayImage (args.Account);
		}
	}
}